﻿//
// Copyright(C) MixModes Inc. 2011
// 

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Windows;
using MixModes.Synergy.Web.VisualFramework.Utilities;

namespace MixModes.Synergy.Web.VisualFramework.Framework
{
    /// <summary>
    /// Observable dependency property collection
    /// </summary>
    /// <typeparam name="T">Type of objects whose property is desired to be monitored</typeparam>
    public class ObservableDependencyPropertyCollection<T> : ObservableCollection<T> where T : DependencyObject
    {
        /// <summary>
        /// Occurs when dependency property has changed
        /// </summary>
        public event DependencyPropertyChangedEventHandler DependencyPropertyChanged;        

        /// <summary>
        /// Initializes a new instance of the <see cref="ObservableDependencyPropertyCollection&lt;T&gt;"/> class.
        /// Only dependency properties specified in properties are monitored
        /// </summary>
        /// <param name="property">Dependency property to monitor in collection</param>
        public ObservableDependencyPropertyCollection(string property)
        {
            Validate.StringNotNullOrEmpty(property, "property");
            _property = property;
        }        
        
        /// <summary>
        /// Raises the <see cref="E:CollectionChanged"/> event.
        /// </summary>
        /// <param name="args">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> 
        /// instance containing the event data.</param>
        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
        {
            base.OnCollectionChanged(args);

            if (_reenterent)
            {
                return;
            }

            _reenterent = true;

            if (args.NewItems != null)
            {
                foreach (DependencyObject obj in args.NewItems)
                {
                    ObservableDependencyProperty descriptor = new ObservableDependencyProperty(_property, OnDependencyPropertyChanged);
                    descriptor.AddValueChanged(obj);
                    _descriptors.Add(descriptor);
                }
            }

            if (args.OldItems != null)
            {                
                HashSet<DependencyObject> removedItems = new HashSet<DependencyObject>();
                foreach (DependencyObject obj in args.OldItems)
                {
                    removedItems.Add(obj);
                }

                List<ObservableDependencyProperty> removedProperties = new List<ObservableDependencyProperty>();

                foreach (ObservableDependencyProperty property in _descriptors)
                {
                    if (removedItems.Contains(property.Target))
                    {
                        property.RemoveValueChanged();
                        removedProperties.Add(property);
                    }
                }
                
                removedProperties.ForEach(item=>_descriptors.Remove(item));
            }

            _reenterent = false;
        }

        /// <summary>
        /// Called when dependency property of an item in collection has changed
        /// </summary>
        /// <param name="item">The item.</param>
        /// <param name="args">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance 
        /// containing the event data.</param>
        protected void OnDependencyPropertyChanged(object item, DependencyPropertyChangedEventArgs args)
        {
            if (DependencyPropertyChanged != null)
            {
                DependencyPropertyChanged(item, args);
            }
        }

        // Private members
        private List<ObservableDependencyProperty> _descriptors = new List<ObservableDependencyProperty>();
        private readonly string _property;
        private bool _reenterent = false;
    }
}